/*
 * Copyright (c) 2006 David Pacheco
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*
 * ctimer v0.01 
 * Simple interface for measuring the real, system, and user times elapsed
 * between two points in the program.
 * 
 * file: ctimer.c
 * desc: implementation of simple C timer
 * auth: dap (dp dot spambait at gmail)
 * date: Nov 22, 2006
 */

#include "ctimer.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>

/*
 * Allocates resources needed for the timer, and initializes it.
 * Note that these timers are one-use. You must reinitialize them, restart them,
 * etc. each time you want to time something.
 * @param ct the timer to initialize (can be allocated any how you like)
 */
void ctimer_init(ctimer_t *ct)
{
	ct->ct_state = CT_INIT;
}

/*
 * Frees resources used by the timer. Note that if you initially allocated the
 * memory, you must still free it. This only frees resources allocated by
 * ctimer_init.
 * @param ct the timer to destroy
 */
void ctimer_destroy(ctimer_t *ct)
{
	/* nothing needed right now. */
}

/*
 * Begins the timer. Data observation functions (currently only ctimer_print)
 * will show the time between ctimer_start and ctimer_end.
 * @param ct the timer to start
 */
void ctimer_start(ctimer_t *ct)
{
	int tres;
	clock_t cres;
	assert(CT_INIT == ct->ct_state);
	
	ct->ct_state = CT_RUN;
	cres = times(&ct->ct_start_tm);
	tres = gettimeofday(&ct->ct_start_tv, NULL);

	assert(cres != (clock_t)-1);
	assert(tres != -1);
}

/*
 * Ends the timer. Data observation functions (currently only ctimer_print)
 * will show the time between ctimer_start and ctimer_end.
 * @param ct the timer to end
 */
void ctimer_end(ctimer_t *ct)
{
	struct timeval end_tv;
	struct tms end_tm;
	clock_t cres;
	int tres;

	cres = times(&end_tm);
	tres = gettimeofday(&end_tv, NULL);
	assert(ct->ct_state == CT_RUN);
	ct->ct_state = CT_DONE;
	
	assert(cres != -1);
	assert(tres != -1);

	/* calculate the differences */
	ct->ct_elapsed_tv.tv_sec = end_tv.tv_sec - ct->ct_start_tv.tv_sec;
	ct->ct_elapsed_tv.tv_usec = end_tv.tv_usec - ct->ct_start_tv.tv_usec;
	if (ct->ct_elapsed_tv.tv_usec < 0) {
		ct->ct_elapsed_tv.tv_sec--;
		ct->ct_elapsed_tv.tv_usec += 1000000;
	}

	ct->ct_elapsed_tm.tms_utime = 
	    end_tm.tms_utime - ct->ct_start_tm.tms_utime;
	ct->ct_elapsed_tm.tms_stime = 
	    end_tm.tms_stime - ct->ct_start_tm.tms_stime;
}

/*
 * utility function: prints a time, given a number of seconds (probably greater
 * than 60) and microseconds
 */
void print_time(const char *label, int sec, int usec)
{
	int hours, min;
	printf(label);
	
	hours = sec / (60 * 60);
	min = sec / 60 % 60;
	sec %= 60;
	assert(usec >= 0 && usec < 1000000);

	if (hours > 0)
		printf("%u:", hours);
	if (min > 0)
		printf("%02u:", min);
	printf("%02u.%06u\n", sec, usec);
}

/*
 * Print the elapsed wall-clock time, user time, and system time to stdout.
 * @param tm the timer (which must have already been stopped)
 */
void ctimer_print(ctimer_t *ct)
{
	double secs, usecs;
	long ticks_per_sec = sysconf(_SC_CLK_TCK);

	print_time("Wall-clock time: ", ct->ct_elapsed_tv.tv_sec, 
	    ct->ct_elapsed_tv.tv_usec);
	
	secs = (double)ct->ct_elapsed_tm.tms_utime / ticks_per_sec;
	usecs = modf(secs, &secs) * 1000000;
	print_time("User time:       ", (int)secs, (int)usecs);
	secs = (double)ct->ct_elapsed_tm.tms_stime / ticks_per_sec;
	usecs = modf(secs, &secs) * 1000000;
	print_time("System time:     ", (int)secs, (int)usecs);
}
